package org.gdstash.db;

import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;
import org.gdstash.util.GDConstants;
import org.gdstash.util.GDMsgFormatter;
import org.gdstash.util.GDMsgLogger;

import java.sql.*;
import java.util.LinkedList;
import java.util.List;

public class DBFormula {
    private static final String TABLE_NAME = "GD_FORMULA";
    private static final int ROW_FORMULASET_ID = 1;
    private static final int ROW_FORMULA_ID = 2;
    private static final int ROW_FORMULA = 3;
    public static final String FORMULA_COST_ARMOR = "armorCostEquation";
    public static final String FORMULA_COST_JEWELRY = "jewelryCostEquation";
    public static final String FORMULA_COST_OFFHAND = "offhandCostEquation";
    public static final String FORMULA_COST_SHIELD = "shieldCostEquation";
    public static final String FORMULA_COST_MELEE_1H = "weaponCostEquation";
    public static final String FORMULA_COST_MELEE_2H = "weaponMelee2hCostEquation";
    public static final String FORMULA_COST_RANGED_1H = "weaponRangedCostEquation";
    public static final String FORMULA_COST_RANGED_2H = "weaponRanged2hCostEquation";
    public static final String FORMULA_DEX_DAGGER = "daggerDexterityEquation";
    public static final String FORMULA_DEX_RANGED_1H = "ranged1hDexterityEquation";
    public static final String FORMULA_DEX_RANGED_2H = "ranged2hDexterityEquation";
    public static final String FORMULA_DEX_SWORD = "swordDexterityEquation";
    public static final String FORMULA_INT_AMULET = "amuletIntelligenceEquation";
    public static final String FORMULA_INT_CHEST = "chestIntelligenceEquation";
    public static final String FORMULA_INT_DAGGER = "daggerIntelligenceEquation";
    public static final String FORMULA_INT_HEAD = "headIntelligenceEquation";
    public static final String FORMULA_INT_OFFHAND = "offhandIntelligenceEquation";
    public static final String FORMULA_INT_RING = "ringIntelligenceEquation";
    public static final String FORMULA_INT_SCEPTER = "scepterIntelligenceEquation";
    public static final String FORMULA_STR_AXE = "axeStrengthEquation";
    public static final String FORMULA_STR_CHEST = "chestStrengthEquation";
    public static final String FORMULA_STR_FEET = "feetStrengthEquation";
    public static final String FORMULA_STR_HANDS = "handsStrengthEquation";
    public static final String FORMULA_STR_HEAD = "headStrengthEquation";
    public static final String FORMULA_STR_LEGS = "legsStrengthEquation";
    public static final String FORMULA_STR_MACE = "maceStrengthEquation";
    public static final String FORMULA_STR_MELEE_2H = "melee2hStrengthEquation";
    public static final String FORMULA_STR_SCEPTER = "scepterStrengthEquation";
    public static final String FORMULA_STR_SHIELD = "shieldStrengthEquation";
    public static final String FORMULA_STR_SHOULDERS = "shouldersStrengthEquation";
    public static final String FORMULA_STR_BELT = "waistStrengthEquation";
    private String formulaSetID;
    private String formulaID;
    private String formula;
    private Expression expression;

    public DBFormula() {
        this.expression = null;
    }

    public String getFormulaSetID() {
        return this.formulaSetID;
    }

    public void setFormulaSetID(final String formulaSetID) {
        this.formulaSetID = formulaSetID;
    }

    public String getFormulaID() {
        return this.formulaID;
    }

    public void setFormulaID(final String formulaID) {
        this.formulaID = formulaID;
    }

    public String getFormula() {
        return this.formula;
    }

    public void setFormula(final String formula) {
        this.formula = formula;
        if (formula != null) {
            ExpressionBuilder builder = new ExpressionBuilder(formula);
            builder = builder.variables(new String[]{"charAttackSpeed", "damageAvgBase", "damageAvgPierceRatio", "defenseAttrArmor", "itemLevel", "itemPrefixCost", "itemSuffixCost", "shieldBlockChance", "shieldBlockDefense", "totalAttCount", "CHARATTACKSPEED", "DAMAGEAVGBASE", "DAMAGEAVGPIERCERATIO", "DEFENSEATTRARMOR", "ITEMLEVEL", "ITEMPREFIXCOST", "ITEMSUFFIXCOST", "SHIELDBLOCKCHANCE", "SHIELDBLOCKDEFENSE", "TOTALATTCOUNT"});
            this.expression = builder.build();
        }
    }

    public void setParameterSet(final ParameterSet ps) {
        if (this.expression == null) {
            return;
        }
        this.expression.setVariable("charAttackSpeed", (double) ps.getCharAttackSpeed());
        this.expression.setVariable("damageAvgBase", (double) ps.getDamageAvgBase());
        this.expression.setVariable("damageAvgPierceRatio", (double) ps.getDamageAvgPierceRatio());
        this.expression.setVariable("defenseAttrArmor", (double) ps.getDefenseAttrArmor());
        this.expression.setVariable("itemLevel", (double) ps.getItemLevel());
        this.expression.setVariable("itemPrefixCost", (double) ps.getItemPrefixCost());
        this.expression.setVariable("itemSuffixCost", (double) ps.getItemSuffixCost());
        this.expression.setVariable("shieldBlockChance", (double) ps.getShieldBlockChance());
        this.expression.setVariable("shieldBlockDefense", (double) ps.getShieldBlockDefense());
        this.expression.setVariable("totalAttCount", (double) ps.getTotalAttCount());
        this.expression.setVariable("CHARATTACKSPEED", (double) ps.getCharAttackSpeed());
        this.expression.setVariable("DAMAGEAVGBASE", (double) ps.getDamageAvgBase());
        this.expression.setVariable("DAMAGEAVGPIERCERATIO", (double) ps.getDamageAvgPierceRatio());
        this.expression.setVariable("DEFENSEATTRARMOR", (double) ps.getDefenseAttrArmor());
        this.expression.setVariable("ITEMLEVEL", (double) ps.getItemLevel());
        this.expression.setVariable("ITEMPREFIXCOST", (double) ps.getItemPrefixCost());
        this.expression.setVariable("ITEMSUFFIXCOST", (double) ps.getItemSuffixCost());
        this.expression.setVariable("SHIELDBLOCKCHANCE", (double) ps.getShieldBlockChance());
        this.expression.setVariable("SHIELDBLOCKDEFENSE", (double) ps.getShieldBlockDefense());
        this.expression.setVariable("TOTALATTCOUNT", (double) ps.getTotalAttCount());
    }

    public double getValue() {
        if (this.expression == null) {
            return 0.0;
        }
        final double value = this.expression.evaluate();
        return value;
    }

    public static void createTable() throws SQLException {
        final String dropTable = "DROP TABLE GD_FORMULA";
        final String createTable = "CREATE TABLE GD_FORMULA (FORMULASET_ID   VARCHAR(256) NOT NULL, FORMULA_ID      VARCHAR(64), FORMULA         VARCHAR(256), PRIMARY KEY (FORMULASET_ID, FORMULA_ID))";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final Statement st = conn.createStatement()) {
                if (GDDBUtil.tableExists(conn, "GD_FORMULA")) {
                    st.execute(dropTable);
                }
                st.execute(createTable);
                st.close();
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {"GD_FORMULA"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_CREATE_TABLE", args);
                GDMsgLogger.addError(msg);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static void delete(final Connection conn, final String formulaSetID) throws SQLException {
        final String deleteEntry = "DELETE FROM GD_FORMULA WHERE FORMULASET_ID = ?";
        try (final PreparedStatement ps = conn.prepareStatement(deleteEntry)) {
            ps.setString(1, formulaSetID);
            ps.executeUpdate();
            ps.close();
        } catch (SQLException ex) {
            throw ex;
        }
    }

    public static void insert(final Connection conn, final DBFormulaSet dbFormulaSet) throws SQLException {
        if (dbFormulaSet.getFormulaList() == null) {
            return;
        }
        if (dbFormulaSet.getFormulaList().isEmpty()) {
            return;
        }
        final String insert = "INSERT INTO GD_FORMULA VALUES (?,?,?)";
        try (final PreparedStatement ps = conn.prepareStatement(insert)) {
            for (final DBFormula formula : dbFormulaSet.getFormulaList()) {
                ps.setString(1, dbFormulaSet.getFormulaSetID());
                ps.setString(2, formula.formulaID);
                ps.setString(3, formula.formula);
                ps.executeUpdate();
                ps.clearParameters();
            }
            ps.close();
            conn.commit();
        } catch (SQLException ex) {
            throw ex;
        }
    }

    public static List<DBFormula> getByFormulaSetID(final String formulaSetID) {
        List<DBFormula> list = null;
        final String command = "SELECT * FROM GD_FORMULA WHERE FORMULASET_ID = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, formulaSetID);
            try (final ResultSet rs = ps.executeQuery()) {
                list = wrap(rs);
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            final Object[] args = {formulaSetID, "GD_FORMULA"};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex2);
        }
        return list;
    }

    private static List<DBFormula> wrap(final ResultSet rs) throws SQLException {
        final LinkedList<DBFormula> list = new LinkedList<DBFormula>();
        while (rs.next()) {
            final DBFormula formula = new DBFormula();
            formula.formulaSetID = rs.getString(1);
            formula.formulaID = rs.getString(2);
            formula.formula = rs.getString(3);
            if (formula.formula != null) {
                formula.formula = formula.formula.toUpperCase(GDConstants.LOCALE_US);
            }
            formula.setFormula(formula.formula);
            list.add(formula);
        }
        return list;
    }
}
